/*
 * Copyright (c) 2010-present www.walkerljl.org All Rights Reserved.
 * The software source code all copyright belongs to the author, 
 * without permission shall not be any reproduction and transmission.
 */
package org.walkerljl.commons.mail;

import javax.mail.Flags;
import javax.mail.Message;
import javax.mail.search.*;

/**
 * <code>EmailFilter</code> helps in building boolean queries of search terms.
 * There are two ways how it can be used, both can be combined.
 * <p>
 * First way is constructing boolean expression using <i>groups</i>.
 * Just use {@link #and(EmailFilter...)}, {@link #or(EmailFilter...)}
 * and {@link #not(EmailFilter)} methods that takes any number
 * of filters that will be joined with chosen boolean operator.
 * <p>
 * Second way is more fluent. It may not be used to express some
 * complex queries, but for every-day use it would be enough.
 * Use methods {@link #and()} and {@link #or()} to define
 * how <b>all next</b> terms will be joined. Method {@link #not()}
 * marks the <b>one next</b> term to be added as NOT term.
 *
 * @author lijunlin
 */
public class EmailFilter {

    protected SearchTerm searchTerm;
    boolean operatorAnd = true;
    boolean nextIsNot;

    /**
     * Creates new Email filter.
     */
    public static EmailFilter filter() {
        return new EmailFilter();
    }

    /**
     * Defines filter for SUBJECT field.
     */
    public EmailFilter subject(String subject) {
        SearchTerm subjectTerm = new SubjectTerm(subject);
        concat(subjectTerm);
        return this;
    }

    /**
     * Defines filter for message id.
     */
    public EmailFilter messageId(String messageId) {
        SearchTerm msgIdTerm = new MessageIDTerm(messageId);
        concat(msgIdTerm);
        return this;
    }

    /**
     * Defines filter for message id.
     */
    public EmailFilter messageId(int messageId) {
        return messageId(String.valueOf(messageId));
    }

    /**
     * Defines filter for FROM field.
     */
    public EmailFilter from(String fromAddress) {
        SearchTerm fromTerm = new FromStringTerm(fromAddress);
        concat(fromTerm);
        return this;
    }

    /**
     * Defines filter for TO field.
     */
    public EmailFilter to(String toAddress) {
        SearchTerm toTerm = new RecipientStringTerm(Message.RecipientType.TO, toAddress);
        concat(toTerm);
        return this;
    }

    /**
     * Defines filter for CC field.
     */
    public EmailFilter cc(String ccAddress) {
        SearchTerm toTerm = new RecipientStringTerm(Message.RecipientType.CC, ccAddress);
        concat(toTerm);
        return this;
    }

    /**
     * Defines filter for BCC field.
     */
    public EmailFilter bcc(String bccAddress) {
        SearchTerm toTerm = new RecipientStringTerm(Message.RecipientType.BCC, bccAddress);
        concat(toTerm);
        return this;
    }

    /**
     * Defines filter for many flags at once.
     */
    public EmailFilter flags(Flags flags, boolean value) {
        SearchTerm flagTerm = new FlagTerm(flags, value);
        concat(flagTerm);
        return this;
    }

    /**
     * Defines filter for single flag.
     */
    public EmailFilter flag(Flags.Flag flag, boolean value) {
        Flags flags = new Flags();
        flags.add(flag);
        return flags(flags, value);
    }


    // ---------------------------------------------------------------- boolean

    /**
     * Changes concatenation mode to AND.
     */
    public EmailFilter and() {
        this.operatorAnd = true;
        return this;
    }

    /**
     * Changes concatenation mode to OR.
     */
    public EmailFilter or() {
        this.operatorAnd = false;
        return this;
    }

    /**
     * Marks next condition to be NOT.
     */
    public EmailFilter not() {
        this.nextIsNot = true;
        return this;
    }

    /**
     * Defines AND group of filters.
     */
    public EmailFilter and(EmailFilter... emailFilters) {
        SearchTerm[] searchTerms = new SearchTerm[emailFilters.length];

        for (int i = 0; i < emailFilters.length; i++) {
            searchTerms[i] = emailFilters[i].searchTerm;
        }

        concat(new AndTerm(searchTerms));
        return this;
    }

    /**
     * Defines OR group of filters.
     */
    public EmailFilter or(EmailFilter... emailFilters) {
        SearchTerm[] searchTerms = new SearchTerm[emailFilters.length];

        for (int i = 0; i < emailFilters.length; i++) {
            searchTerms[i] = emailFilters[i].searchTerm;
        }

        concat(new OrTerm(searchTerms));
        return this;
    }

    /**
     * Appends single filter as NOT.
     */
    public EmailFilter not(EmailFilter emailFilter) {
        SearchTerm searchTerm = new NotTerm(emailFilter.searchTerm);
        concat(searchTerm);
        return this;
    }

    // ---------------------------------------------------------------- concat

    /**
     * Concatenates last search term with new one.
     */
    protected void concat(SearchTerm searchTerm) {
        if (nextIsNot) {
            searchTerm = new NotTerm(searchTerm);
            nextIsNot = false;
        }
        if (operatorAnd) {
            and(searchTerm);
        } else {
            or(searchTerm);
        }
    }

    protected void and(SearchTerm searchTerm) {
        if (this.searchTerm == null) {
            this.searchTerm = searchTerm;
            return;
        }

        this.searchTerm = new AndTerm(this.searchTerm, searchTerm);
    }

    protected void or(SearchTerm searchTerm) {
        if (this.searchTerm == null) {
            this.searchTerm = searchTerm;
            return;
        }

        this.searchTerm = new OrTerm(this.searchTerm, searchTerm);
    }

    // ---------------------------------------------------------------- term

    /**
     * Returns search term.
     */
    public SearchTerm getSearchTerm() {
        return searchTerm;
    }

}